#include <bits/stdc++.h>
using namespace std;

// 表达边(u,v)w
struct edge
{
    int start; // 起点
    int to;    // 终点
    int weight;
};

edge edges[200001];
int n, m;
int totalWeight;

// 并查集
int f[5001];

void initDisjointSet()
{
    for(int i=1; i<=m; i++)
        f[i] = i;
}

// 找到指定元素的并查集的根节点
int root(int x)
{
    if(f[x] == x)
        return x;

    f[x] = root(f[x]);
    return f[x];
}

void merge(int u, int v)
{
    if(root(u) == root(v))
        return;

    f[root(u)] = root(v);
}

// 比较器
bool cmp(edge a, edge b)
{
    // 将边的权值按从小到大排序
    return a.weight < b.weight;
}

int main(int argc, char const *argv[])
{
    // 输入n个节点，m条边
    cin >> n >> m;

    // 输入每一条边及期权值x y z
    for(int i=1; i<=m; i++)
        cin >> edges[i].start >> edges[i].to >> edges[i].weight;


    // 初始化并查集
    initDisjointSet();


    // 克鲁斯卡尔算法一般步骤
    // 1. 将权值进行排序
    sort( &edges[1],  &edges[m+1], cmp);

    // 2. 从最小权值的边开始，不断加入到MST集合中
    int i, u, v;
    for(i=1; i<=m; i++)
    {
        // 试图做这件事：edges[i] --> MST（用并查集来表达这棵最小生成树）
        u = root(edges[i].start);
        v = root(edges[i].to);

        // 如果u和v已经属于同一个组，那么就跳过
        if(u == v)
            continue;

        // 否则，就该边的两个端点合并到并查集中
        merge(edges[i].start, edges[i].to);

        // 累加、统计最小的权值
        totalWeight += edges[i].weight;

        // 判定是否已经添加了n-1条边
        if(i == n-1)
            break;
    }

    // 输出答案
    if(i != n-1)
        cout << "orz" << endl;
    else
        cout << totalWeight << endl;

    return 0;
}
